using System;
using System.Data;
using System.Data.Common;
using System.Reflection;
using System.Collections.Generic;

namespace Tilver.DBConnectionUI
{
    internal class DbProvider
    {
        public string InvariantName { get; private set; }
        public string Description { get; private set; }
        private Type ProviderType { get; set; }
        private bool IsFactory { get; set; }
        private bool IsGAC { get; set; }

        internal DbProvider(DataRow providerRow)
        {
            InvariantName = providerRow["InvariantName"].ToString();
            Description = providerRow["Description"].ToString();
            IsFactory = true;
            IsGAC = true;
        }

        public DbProvider(string invariantName, Type providerType) :
            this(invariantName, invariantName, providerType) { }

        public DbProvider(string invariantName,string description,Type providerType)
        {
            bool factory = providerType.IsSubclassOf(typeof(System.Data.Common.DbProviderFactory));        
            bool conn = typeof(IDbConnection).IsAssignableFrom(providerType);

            // only accept valid _providerType parameters
            if (!factory && !conn)
                throw new ArgumentException("providerType must be of Type DbProviderFactory or IDbConnection");
            
            InvariantName = invariantName;
            Description = description;
            ProviderType = providerType;
            IsFactory = factory;
            IsGAC = false;
        }

        /// <summary>
        /// Returns a new IDbConnection for this provider
        /// </summary>
        public IDbConnection GetConnection()
        {
            // if the provider supports factories we'll use this feature to
            // get the connection otherwise we'll try creating the connection using Activator.
            if (!this.IsFactory)
                return Activator.CreateInstance(this.ProviderType) as IDbConnection;
            else
            {
                DbProviderFactory factory = this.GetFactory();
                if (factory != null)
                {
                    return factory.CreateConnection();
                }
            }
            return null;
        }

        /// <summary>
        /// Returns the DbProviderFactory for the provider or null if the
        /// provider doesn't supports factories
        /// </summary>
        public DbProviderFactory GetFactory()
        {
            if (this.IsFactory)
            {
                if (this.IsGAC)
                {
                    try
                    {
                        // get the factory instance
                        return DbProviderFactories.GetFactory(this.InvariantName);
                    }
                    catch (System.Configuration.ConfigurationException)
                    {
                        // the provider is not registered correctly
                    }
                }
                else
                {
                    // Usually DbFactories are implemented as singleton, for this reason 
                    // we need to retrieve the runnig instance using reflection
                    FieldInfo fld = this.ProviderType.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);
                    if ((fld != null) && fld.FieldType.IsSubclassOf(typeof(DbProviderFactory)))
                    {
                        object instance = fld.GetValue(null);
                        if (instance != null)
                        {
                            return instance as DbProviderFactory;
                        }
                    }
                }
            }
            return null;
        }

        public bool TestConnection(string connString, out string msg)
        {
            try
            {
                IDbConnection conn = this.GetConnection();
                conn.ConnectionString = connString;
                conn.Open();
                conn.Close();
                msg = "Test succesfully";
                return true;
            }
            catch (Exception ex)
            {
                msg = ex.Message;
                return false;
            }
        }

        public override string ToString()
        {
            return this.Description;
        }
    }
}
